/*
 * Copyright 2010 Alessio Stalla.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dynaspring;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.armedbear.lisp.JavaObject;
import org.armedbear.lisp.Lisp;
import org.armedbear.lisp.LispObject;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PublicBeanDefinitionValueResolver;
import org.springframework.beans.factory.support.RootBeanDefinition;

public class LispBeanFactory extends DefaultListableBeanFactory {
	
	public static final String LISTENERS_ATTRIBUTE = "dynaspring.listeners";
	public static final String POSTPROCESSOR_ATTRIBUTE = "dynaspring.postprocessor";
	
	private static final Log log = LogFactory.getLog(LispBeanFactory.class);
	private static final LispObject BEFORE = Lisp.PACKAGE_KEYWORD.internAndExport("BEFORE");
	private static final LispObject AFTER = Lisp.PACKAGE_KEYWORD.internAndExport("AFTER");

	public LispBeanFactory() {}
	
	public LispBeanFactory(BeanFactory parentBeanFactory) {
		super(parentBeanFactory);
	}
	
	@Override
	protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
		if(mbd != null && mbd.getAttribute(POSTPROCESSOR_ATTRIBUTE) != null) {
			if(log.isDebugEnabled()) { log.debug("Applying :before bean post-processor to " + beanName); }
			bean = applyPostProcessor(beanName, mbd, bean, BEFORE);
		}
		bean = super.initializeBean(beanName, bean, mbd);
		if(mbd != null && mbd.getAttribute(POSTPROCESSOR_ATTRIBUTE) != null) {
			if(log.isDebugEnabled()) { log.debug("Applying :after bean post-processor to " + beanName); }
			bean = applyPostProcessor(beanName, mbd, bean, AFTER);
		}
		return bean;
	}
	
	@Override
	protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) {
		super.populateBean(beanName, mbd, bw);
		if(mbd.getAttribute(LISTENERS_ATTRIBUTE) != null) {
			if(log.isDebugEnabled()) { log.debug("Injecting \"listeners\" into " + beanName); }
			populateListeners(beanName, mbd, bw);
		}
	}

	protected void populateListeners(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) {
		PublicBeanDefinitionValueResolver valueResolver = new PublicBeanDefinitionValueResolver(this, beanName, mbd, bw);
		List<?> listeners = (List<?>) mbd.getAttribute(LISTENERS_ATTRIBUTE);
		for(Iterator<?> it = listeners.iterator(); it.hasNext();) {
			Object methodNameObj = it.next();
			if(methodNameObj instanceof String) {
				if(!it.hasNext()) {
					throw new BeanCreationException("No listeners supplied for " + methodNameObj + " when trying to add listeners to " + beanName);	
				}
				String methodName = (String) methodNameObj;
				Object value = valueResolver.resolveValueIfNecessary("listener", it.next());
				if(value != null) {
					try {
						Method method = null;
						Class<?> bestMatch = Object.class;
						Class<?> targetClass = bw.getWrappedClass();
						for(Method m : targetClass.getMethods()) {
							if(m.getName().equals(methodName)) {
								Class<?>[] params = m.getParameterTypes();
								if(params.length == 1) {
									if(bestMatch.isAssignableFrom(params[0])) { //I found a better match
										bestMatch = params[0];
										method = m;
									}
								}
							}
						}
						if(method != null) {
							method.invoke(bw.getWrappedInstance(), value);
						} else {
							throw new NoSuchMethodException("No method named " + methodName + " applicable to " + value + " found in " + targetClass);
						}
					} catch (Exception e) {
						throw new BeanCreationException("Couldn't invoke method " + methodName + " to add listeners to " + beanName, e);
					}						
				} else {
					throw new BeanCreationException("Null listeners are not allowed (bean: " + beanName + " method: " + methodName + ")");
				}
			} else {
				throw new BeanCreationException("Invalid method name " + methodNameObj + " when trying to add listeners to " + beanName);
			}
		}
	}
	
	protected Object applyPostProcessor(String beanName, AbstractBeanDefinition mbd, Object bean, LispObject key) {
		LispObject fn = (LispObject) mbd.getAttribute(POSTPROCESSOR_ATTRIBUTE);
		try {
			return fn.execute(JavaObject.getInstance(bean), key).javaInstance();
		} catch(Throwable t) {
			throw new RuntimeException("Exception thrown while applying post processor to bean " + beanName, t);
		}
	}
	
}
